package cn.denvie.agent.collector;

import cn.denvie.agent.common.AgentConfig;
import cn.denvie.agent.common.AgentContext;
import cn.denvie.agent.common.ByteAgentBuilder;
import cn.denvie.agent.common.MethodBuilder;
import cn.denvie.agent.logger.ILogger;
import cn.denvie.agent.logger.JclLogger;
import cn.denvie.agent.model.ClassStatistics;
import cn.denvie.agent.utils.StringUtils;
import cn.denvie.agent.utils.WildcardMatcher;
import javassist.*;

import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * Class方法执行性能数据采集。
 *
 * @author DengZhaoyong
 * @version 1.0.0
 */
public class ClassCollector extends AbstractByteTransformCollector implements ICollector {

    public static ClassCollector INSTANCE;

    private static ILogger logger = new JclLogger(ClassCollector.class.getName());

    private final AgentContext context;
    private final String hostIp;
    private final String hostName;
    private List<WildcardMatcher> includeMatchers = new ArrayList<>();  // 包含哪些类
    private List<WildcardMatcher> excludeMatchers = new ArrayList<>();  // 排除非哪些类

    private static final String beginSrc;
    private static final String endSrc;
    private static final String errorSrc;

    static {
        StringBuilder builder = new StringBuilder();
        builder.append("cn.denvie.agent.collector.ClassCollector instance = ");
        builder.append("cn.denvie.agent.collector.ClassCollector.INSTANCE;\r\n");
        builder.append("cn.denvie.agent.model.ClassStatistics statistic = instance.begin(\"%s\",\"%s\");");
        beginSrc = builder.toString();
        builder = new StringBuilder();
        builder.append("instance.end(statistic);");
        endSrc = builder.toString();
        builder = new StringBuilder();
        builder.append("instance.error(statistic, e);");
        errorSrc = builder.toString();
    }

    // init include, exclude
    public ClassCollector(final AgentContext context, final Instrumentation instrumentation) {
        super(instrumentation);
        this.context = context;
        this.hostIp = context.getConfig(AgentConfig.HOST_IP, "Undefine");
        this.hostName = context.getConfig(AgentConfig.HOST_NAME, "Undefine");
        String include = context.getConfig(AgentConfig.INCLUDE_PATH, "");
        if (!StringUtils.isEmpty(include)) {
            List<String> includeList = separateByComma(include);
            for (String s : includeList) {
                this.includeMatchers.add(new WildcardMatcher(s.trim()));
            }
        } else {
            System.err.println("未配置[" + AgentConfig.INCLUDE_PATH + "]参数，无法插入监控字节码！");
        }
        String exclude = context.getConfig(AgentConfig.EXCLUDE_PATH, "");
        if (!StringUtils.isEmpty(exclude)) {
            List<String> excludeList = separateByComma(exclude);
            for (String s : excludeList) {
                this.excludeMatchers.add(new WildcardMatcher(s.trim()));
            }
        }
        addDefaultExclude();
        INSTANCE = this;
    }

    // 添加默认排除插桩的类路径
    private void addDefaultExclude() {
        this.excludeMatchers.add(new WildcardMatcher("java.*"));
        this.excludeMatchers.add(new WildcardMatcher("javax.*"));
        this.excludeMatchers.add(new WildcardMatcher("sun.*"));
        this.excludeMatchers.add(new WildcardMatcher("org.junit.*"));
        this.excludeMatchers.add(new WildcardMatcher("org.apache.*"));
        this.excludeMatchers.add(new WildcardMatcher("org.springframework.*"));
        this.excludeMatchers.add(new WildcardMatcher("javassist.*"));
    }

    public ClassStatistics begin(String className, String methodName) {
        ClassStatistics bean = new ClassStatistics();
        bean.setTraceId(UUID.randomUUID().toString());
        bean.setHostIp(hostIp);
        bean.setHostName(hostName);
        bean.setBegin(System.currentTimeMillis());
        bean.setClassName(className);
        bean.setMethodName(methodName);
        bean.setSimpleName(className.substring(className.lastIndexOf(".") + 1));
        bean.setAgentType("class");
        bean.setRecordTime(System.currentTimeMillis());
        return bean;
    }

    public void error(ClassStatistics bean, Throwable e) {
        bean.setErrorType(e.getClass().getName());
        bean.setErrorMsg(e.getMessage());
    }

    public void end(ClassStatistics bean) {
        bean.setEnd(System.currentTimeMillis());
        bean.setUseTime(bean.getEnd() - bean.getBegin());
        context.submitCollectResult(bean);
    }

    @Override
    public byte[] transform(ClassLoader loader, String className)
            throws CannotCompileException, NotFoundException, IOException {

        if (!isMatch(includeMatchers, className)) { // 包含指定类
            return null;
        } else if (isMatch(excludeMatchers, className)) { // 排除指定类
            return null;
        }

        logger.info("==>> Agent Transform, className: " + className);

        CtClass ctclass = toCtClass(loader, className);
        ByteAgentBuilder byteBuilder = new ByteAgentBuilder(className, loader, ctclass);
        CtMethod[] methods = ctclass.getDeclaredMethods();
        for (CtMethod m : methods) {
            // 屏蔽非公共方法
            if (!Modifier.isPublic(m.getModifiers())) {
                continue;
            }
            // 屏蔽静态方法
            if (Modifier.isStatic(m.getModifiers())) {
                continue;
            }
            // 屏蔽本地方法
            if (Modifier.isNative(m.getModifiers())) {
                continue;
            }

            logger.info("  >> Agent Transform, methodName: " + m.getName());

            MethodBuilder builder = new MethodBuilder();
            builder.setBeginSrc(String.format(beginSrc, className, m.getName()));
            builder.setEndSrc(endSrc);
            builder.setErrorSrc(errorSrc);

            byteBuilder.updateMethod(m, builder);
        }
        return byteBuilder.toBytecote();
    }

    private boolean isMatch(List<WildcardMatcher> matchers, String text) {
        if (matchers == null || matchers.isEmpty() || StringUtils.isEmpty(text)) {
            return false;
        }
        for (WildcardMatcher matcher : matchers) {
            if (matcher.matches(text)) {
                return true;
            }
        }
        return false;
    }
}